package com.zhang.findkeyword.utils;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author zhaohaijun
 */
public class BashUtils {

    private static Logger logger = LoggerFactory.getLogger(BashUtils.class);

    /**
     * Bash进程超时时间，单位：秒
     */
    public static final int processTimeOutSeconds = 15;

    /**
     * 以字符串形式返回标准输出
     *
     * @param command
     * @return
     * @throws IOException
     */
    public static String executeReString(String command) {
        Process process = null;
        InputStream inputStream = null;
        InputStream errorStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader inputBufferedReader = null;
        StringBuilder stdout = new StringBuilder("");
        try {
            process = Runtime.getRuntime().exec(command);
            if (logger.isDebugEnabled()) {
                logger.debug("processId = {}, command = {}", unixLikeProcessId(process), command);
            }
            process.waitFor(processTimeOutSeconds, TimeUnit.SECONDS);
            inputStream = process.getInputStream();
            errorStream = process.getErrorStream();
            inputStreamReader = new InputStreamReader(inputStream);
            inputBufferedReader = new BufferedReader(inputStreamReader);
            String tmp = "";
            while ((tmp = inputBufferedReader.readLine()) != null) {
                stdout.append(tmp);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeStream(process, inputStream, errorStream, inputStreamReader, null, inputBufferedReader, null);
        }
        return stdout.toString();
    }

    /**
     * 以字符串形式返回标准输出，无标准输出则返回错误输出
     *
     * @param command
     * @return
     * @throws IOException
     */
    public static String executeReStringWithError(String command) {
        Process process = null;
        InputStream inputStream = null;
        InputStream errorStream = null;
        InputStreamReader inputStreamReader = null;
        InputStreamReader errorStreamReader = null;
        BufferedReader inputBufferedReader = null;
        BufferedReader errorBufferedReader = null;
        StringBuilder stdout = new StringBuilder();
        try {
            process = Runtime.getRuntime().exec(command);
            if (logger.isDebugEnabled()) {
                logger.debug("processId = {}, command = {}", unixLikeProcessId(process), command);
            }
            process.waitFor(processTimeOutSeconds, TimeUnit.SECONDS);
            inputStream = process.getInputStream();
            errorStream = process.getErrorStream();
            inputStreamReader = new InputStreamReader(inputStream);
            errorStreamReader = new InputStreamReader(errorStream);
            inputBufferedReader = new BufferedReader(inputStreamReader);
            errorBufferedReader = new BufferedReader(errorStreamReader);
            String tmp = "";
            while ((tmp = inputBufferedReader.readLine()) != null) {
                stdout.append(tmp);
            }
            if (StringUtils.isBlank(stdout.toString())) {
                while ((tmp = errorBufferedReader.readLine()) != null) {
                    stdout.append(tmp);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeStream(process, inputStream, errorStream, inputStreamReader, errorStreamReader, inputBufferedReader, errorBufferedReader);
        }
        return stdout.toString();
    }

    /**
     * 将标准输出以列表形式返回
     *
     * @param command
     * @return
     * @throws IOException
     */
    public static List<String> executeReList(String command) {
        Process process = null;
        InputStream inputStream = null;
        InputStream errorStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader inputBufferedReader = null;
        List<String> stdoutList = new ArrayList<>();
        try {
            process = Runtime.getRuntime().exec(command);
            if (logger.isDebugEnabled()) {
                logger.debug("processId = {}, command = {}", unixLikeProcessId(process), command);
            }
            process.waitFor(processTimeOutSeconds, TimeUnit.SECONDS);
            inputStream = process.getInputStream();
            errorStream = process.getErrorStream();
            inputStreamReader = new InputStreamReader(inputStream);
            inputBufferedReader = new BufferedReader(inputStreamReader);
            String tmp = "";
            while ((tmp = inputBufferedReader.readLine()) != null) {
                if (StringUtils.isNotBlank(tmp)) {
                    stdoutList.add(tmp);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeStream(process, inputStream, errorStream, inputStreamReader, null, inputBufferedReader, null);
        }
        return stdoutList;
    }

    /**
     * 以列表形式返回标准输出，无标准输出则返回错误输出
     *
     * @param command
     * @return
     * @throws IOException
     */
    public static List<String> executeReListWithError(String command) {
        Process process = null;
        InputStream inputStream = null;
        InputStream errorStream = null;
        InputStreamReader inputStreamReader = null;
        InputStreamReader errorStreamReader = null;
        BufferedReader inputBufferedReader = null;
        BufferedReader errorBufferedReader = null;
        List<String> stdoutList = new ArrayList<>();
        try {
            process = Runtime.getRuntime().exec(command);
            if (logger.isDebugEnabled()) {
                logger.debug("processId = {}, command = {}", unixLikeProcessId(process), command);
            }
            process.waitFor(processTimeOutSeconds, TimeUnit.SECONDS);
            inputStream = process.getInputStream();
            errorStream = process.getErrorStream();
            inputStreamReader = new InputStreamReader(inputStream);
            errorStreamReader = new InputStreamReader(errorStream);
            inputBufferedReader = new BufferedReader(inputStreamReader);
            errorBufferedReader = new BufferedReader(errorStreamReader);
            String tmp = "";
            while ((tmp = inputBufferedReader.readLine()) != null) {
                if (StringUtils.isNotBlank(tmp)) {
                    stdoutList.add(tmp);
                }
            }
            if (stdoutList.size() <= 0) {
                while ((tmp = errorBufferedReader.readLine()) != null) {
                    if (StringUtils.isNotBlank(tmp)) {
                        stdoutList.add(tmp);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeStream(process, inputStream, errorStream, inputStreamReader, errorStreamReader, inputBufferedReader, errorBufferedReader);
        }
        return stdoutList;
    }

    /**
     * 以列表形式返回标准输出，支持管道
     *
     * @param command
     * @return
     * @throws IOException
     */
    public static List<String> executeWithPipelineReList(String command) {
        Process process = null;
        InputStream inputStream = null;
        InputStream errorStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader inputBufferedReader = null;
        List<String> stdoutList = new ArrayList<>();
        try {
            List<String> cmds = new ArrayList<String>();
            cmds.add("bash");
            cmds.add("-c");
            cmds.add(command);
            ProcessBuilder processBuilder = new ProcessBuilder(cmds);
            process = processBuilder.start();
            if (logger.isDebugEnabled()) {
                logger.debug("processId = {}, command = {}", unixLikeProcessId(process), command);
            }
            process.waitFor(processTimeOutSeconds, TimeUnit.SECONDS);
            inputStream = process.getInputStream();
            errorStream = process.getErrorStream();
            inputStreamReader = new InputStreamReader(inputStream);
            inputBufferedReader = new BufferedReader(inputStreamReader);
            String tmp = "";
            while ((tmp = inputBufferedReader.readLine()) != null) {
                if (StringUtils.isNotBlank(tmp)) {
                    stdoutList.add(tmp);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeStream(process, inputStream, errorStream, inputStreamReader, null, inputBufferedReader, null);
        }
        return stdoutList;
    }

    /**
     * 以列表形式返回标准输出，无标准输出则返回错误输出，支持管道
     *
     * @param command
     * @return
     * @throws IOException
     */
    public static List<String> executeWithPipelineReListWithError(String command) {
        Process process = null;
        InputStream inputStream = null;
        InputStream errorStream = null;
        InputStreamReader inputStreamReader = null;
        InputStreamReader errorStreamReader = null;
        BufferedReader inputBufferedReader = null;
        BufferedReader errorBufferedReader = null;
        List<String> stdoutList = new ArrayList<>();
        try {
            List<String> cmds = new ArrayList<String>();
            cmds.add("bash");
            cmds.add("-c");
            cmds.add(command);
            ProcessBuilder processBuilder = new ProcessBuilder(cmds);
            process = processBuilder.start();
            if (logger.isDebugEnabled()) {
                logger.debug("processId = {}, command = {}", unixLikeProcessId(process), command);
            }
            process.waitFor(processTimeOutSeconds, TimeUnit.SECONDS);
            inputStream = process.getInputStream();
            errorStream = process.getErrorStream();
            inputStreamReader = new InputStreamReader(inputStream);
            errorStreamReader = new InputStreamReader(errorStream);
            inputBufferedReader = new BufferedReader(inputStreamReader);
            errorBufferedReader = new BufferedReader(errorStreamReader);
            String tmp = "";
            while ((tmp = inputBufferedReader.readLine()) != null) {
                if (StringUtils.isNotBlank(tmp)) {
                    stdoutList.add(tmp);
                }
            }
            if (stdoutList.size() <= 0) {
                while ((tmp = errorBufferedReader.readLine()) != null) {
                    if (StringUtils.isNotBlank(tmp)) {
                        stdoutList.add(tmp);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeStream(process, inputStream, errorStream, inputStreamReader, errorStreamReader, inputBufferedReader, errorBufferedReader);
        }
        return stdoutList;
    }

    /**
     * 以字符串形式返回标准输出，支持管道
     *
     * @param command
     * @return
     * @throws IOException
     */
    public static String executeWithPipelineReString(String command) {
        Process process = null;
        InputStream inputStream = null;
        InputStream errorStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader inputBufferedReader = null;
        StringBuilder stdout = new StringBuilder("");
        try {
            List<String> cmds = new ArrayList<String>();
            cmds.add("bash");
            cmds.add("-c");
            cmds.add(command);
            ProcessBuilder processBuilder = new ProcessBuilder(cmds);
            process = processBuilder.start();
            if (logger.isDebugEnabled()) {
                logger.debug("processId = {}, command = {}", unixLikeProcessId(process), command);
            }
            process.waitFor(processTimeOutSeconds, TimeUnit.SECONDS);
            inputStream = process.getInputStream();
            errorStream = process.getErrorStream();
            inputStreamReader = new InputStreamReader(inputStream);
            inputBufferedReader = new BufferedReader(inputStreamReader);
            String tmp = "";
            while ((tmp = inputBufferedReader.readLine()) != null) {
                stdout.append(tmp);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeStream(process, inputStream, errorStream, inputStreamReader, null, inputBufferedReader, null);
        }
        return stdout.toString();
    }

    /**
     * 以字符串形式返回标准输出，无标准输出则返回错误输出，支持管道
     *
     * @param command
     * @return
     * @throws IOException
     */
    public static String executeWithPipelineReStringWithError(String command) {
        Process process = null;
        InputStream inputStream = null;
        InputStream errorStream = null;
        InputStreamReader inputStreamReader = null;
        InputStreamReader errorStreamReader = null;
        BufferedReader inputBufferedReader = null;
        BufferedReader errorBufferedReader = null;
        StringBuilder stdout = new StringBuilder("");
        try {
            List<String> cmds = new ArrayList<>();
            cmds.add("bash");
            cmds.add("-c");
            cmds.add(command);
            ProcessBuilder processBuilder = new ProcessBuilder(cmds);
            process = processBuilder.start();
            if (logger.isDebugEnabled()) {
                logger.debug("processId = {}, command = {}", unixLikeProcessId(process), command);
            }
            process.waitFor(processTimeOutSeconds, TimeUnit.SECONDS);
            inputStream = process.getInputStream();
            errorStream = process.getErrorStream();
            inputStreamReader = new InputStreamReader(inputStream);
            errorStreamReader = new InputStreamReader(errorStream);
            inputBufferedReader = new BufferedReader(inputStreamReader);
            errorBufferedReader = new BufferedReader(errorStreamReader);
            String tmp = "";
            while ((tmp = inputBufferedReader.readLine()) != null) {
                stdout.append(tmp);
            }
            if (StringUtils.isBlank(stdout.toString())) {
                while ((tmp = errorBufferedReader.readLine()) != null) {
                    stdout.append(tmp);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeStream(process, inputStream, errorStream, inputStreamReader, errorStreamReader, inputBufferedReader, errorBufferedReader);
        }
        return stdout.toString();
    }

    private static void closeStream(Process process, InputStream inputStream, InputStream errorStream, InputStreamReader inputStreamReader, InputStreamReader errorStreamReader, BufferedReader inputBufferedReader, BufferedReader errorBufferedReader) {
        try {
            if (inputBufferedReader != null) {
                inputBufferedReader.close();
            }
            if (errorBufferedReader != null) {
                errorBufferedReader.close();
            }
            if (inputStreamReader != null) {
                inputStreamReader.close();
            }
            if (errorStreamReader != null) {
                errorStreamReader.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
            if (errorStream != null) {
                errorStream.close();
            }
            if (process != null) {
                Long processId = unixLikeProcessId(process);
                if (null != processId && process.isAlive()) {
                    String command = "kill -9 " + processId;
                    if (logger.isInfoEnabled()) {
                        logger.info("Killed bash process {}", processId);
                    }
                    Runtime.getRuntime().exec(command);
                }
                process.destroy();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static Long unixLikeProcessId(Process process) {
        Class<?> clazz = process.getClass();
        try {
            if (clazz.getName().equals("java.lang.UNIXProcess")) {
                Field pidField = clazz.getDeclaredField("pid");
                pidField.setAccessible(true);
                Object value = pidField.get(process);
                if (value instanceof Integer) {
                    return ((Integer) value).longValue();
                }
            }
        } catch (SecurityException | IllegalAccessException | IllegalArgumentException | NoSuchFieldException sx) {
            sx.printStackTrace();
        }
        return null;
    }

}
